import pygame
from pygame.locals import *
import time
import sys
import copy

import heapq

movingAtX = 20
movingAtY = 10

class ReceivedQuitEvent ( Exception ): pass
class FontMustBeFixed (Exception ): pass

class FontCache:
	def __init__(self, font, antialiasing = True, cacheSize = None):
		self.font = font
		self.antialiasing = antialiasing
		self.alphabet = []
		for i in range(256):
			if i < 32 or i > 127:
				self.alphabet.append( " " )
			else:
				self.alphabet.append( chr(i) )
		self.alphabet = "".join( self.alphabet ) 
		characterWidths = [ self.font.size( letter )[0] for letter in self.alphabet ]
		if max( characterWidths ) != min( [ cW for cW in characterWidths if cW > 0 ] ):
			pass
			#print >>sys.stderr, max( characterWidths ) , min( [ cW for cW in characterWidths if cW > 0 ] )
			#print >>sys.stderr, "Font is not being ideally rendered."
		self.width, self.height = self.size = max( characterWidths ), self.font.get_height()
		self.surfaces = {}
		self.cacheSize = cacheSize
	def preparePair(self, fgbg):
		foreground, background = fgbg
		self.cull()
		ar = []
		for letter in self.alphabet:
			try:
				l = self.font.render( letter, self.antialiasing, foreground, background )
				s = pygame.Surface( (self.width, self.height) )
				s.fill( background )
				s.blit( l, (0,0) )
				ar.append( s )
			except pygame.error:
				ar.append( None )
				pass
		self.surfaces[ (foreground,background) ] = [ ar, time.time() ]
	def getAlphabet(self, fgbg):
		if not self.surfaces.has_key( fgbg ):
			self.preparePair( fgbg )
		self.surfaces[fgbg][1] = time.time()
		return self.surfaces[fgbg][0]
	def cull(self):
		if self.cacheSize and len(self.surfaces) > self.cacheSize[1]:
			values = [ (self.surfaces[(fg,bg)][1], (fg,bg)) for fg,bg in self.surfaces.keys() ]
			values.sort()
			values = values[self.cacheSize[0]+1:]
			for lastUsed, key in values:
				self.surfaces.pop(key)

class MonochromeBitmapFont:
	def __init__(self, fontFile, background = (0,0,0) ):
		fullImage = pygame.image.load( fontFile ).convert( 32 )
		fullWidth, fullHeight = fullImage.get_size()
		bg = fullImage.map_rgb( background )
		assert fullWidth % 16 == 0
		assert fullHeight % 16 == 0
		self.glyphWidth = fullWidth // 16
		self.glyphHeight = fullHeight // 16
		self.size = self.glyphWidth, self.glyphHeight
		self.letterCache = {}
		self.backgroundsW = []
		self.backgroundsB = []
		for i in range(256):
			x = (i % 16) * self.glyphWidth
			y = (i // 16) * self.glyphHeight
			letter = pygame.Surface( (self.glyphWidth, self.glyphHeight), 32 )
			letter.blit( fullImage, (0,0), (x,y,self.glyphWidth,self.glyphHeight) )
			letterArray = pygame.surfarray.pixels2d( letter )
			bgB = pygame.Surface( (self.glyphWidth, self.glyphHeight), 32 )
			bgBa = pygame.surfarray.pixels2d( bgB )
			bgW = pygame.Surface( (self.glyphWidth, self.glyphHeight), 32 )
			bgWa = pygame.surfarray.pixels2d( bgB )
			for x in range(self.glyphWidth):
				for y in range(self.glyphHeight):
					if letterArray[x][y] == bg:
						bgWa[x][y] = bgW.map_rgb( (255,255,255) )
						bgBa[x][y] = bgB.map_rgb( (0,0,0) )
					else:
						bgWa[x][y] = bgW.map_rgb( (0,0,0) )
						bgBa[x][y] = bgB.map_rgb( (255,255,255) )
			bgB.set_colorkey( (255,255,255), pygame.RLEACCEL )
			bgW.set_colorkey( (0,0,0), pygame.RLEACCEL )
			self.backgroundsB.append( bgB )
			self.backgroundsW.append( bgW )
	def getLetter(self, letterNo, fg):
		key = letterNo, fg
		if not self.letterCache.has_key( key ):
			s = pygame.Surface( (self.glyphWidth, self.glyphHeight)  )
			s.fill( fg )
			if fg == (0,0,0):
				s.blit( self.backgroundsW[ letterNo ], (0,0) )
				s.set_colorkey( (255,255,255), pygame.RLEACCEL )
			else:
				s.blit( self.backgroundsB[ letterNo ], (0,0) )
				s.set_colorkey( (0,0,0), pygame.RLEACCEL )
			self.letterCache[key] = [ None, s ]
		self.letterCache[key][0] = time.time()
		return self.letterCache[key][1]
	def drawLetter(self, surface, xy, letterNo, fg, bg):
		x0,y0 = xy
		surface.fill( bg, (x0,y0,self.glyphWidth,self.glyphHeight) )
		if fg != bg:
			img = self.getLetter( letterNo, fg )
			surface.blit( img, (x0,y0) )
	def cull(self, limit, tolerance):
		if len( self.letterCache ) > tolerance:
			assert tolerance > limit
			q = []
			for key in self.letterCache.keys():
				q.append( ( self.letterCache[key][0], key ) )
			self.letterCache[key]
			heapq.heapify( q )
			while len( self.letterCache ) > limit:
				t, key = heapq.heappop( q )
				self.letterCache.pop( key )
				

class PygameAscii:
	def __init__(self, title, rowsCols, font, forceResizable = False, fontCacheSoftLimit = 2000, fontCacheTolerance = None ):
		pygame.init()
		pygame.display.set_caption( title )
		self.cols, self.rows = rowsCols
		## tt fonts
		fontFile, fontSize = font
		self.fontCache = FontCache( pygame.font.Font( fontFile, fontSize ), cacheSize=(2000,4000) )
		self.cellWidth, self.cellHeight = self.fontCache.size
		## end tt fonts
		### self.fontProvider = MonochromeBitmapFont( font )
		### self.cellWidth, self.cellHeight = self.fontProvider.size
		flags = 0
		if forceResizable: flags |= pygame.RESIZABLE
		self.screen = pygame.display.set_mode( (self.cols * self.cellWidth, self.rows * self.cellHeight), flags )
		self.fpsUpdates = []
		self.fpsPeriod = 5.0
		self.started = time.time()
		self.buffer = []
		self.drawn = []
		for j in range(self.cols):
			bufferLine = []
			drawnLine = []
			for i in range(self.rows):
				black = ord(' '), ((0,0,0),(0,0,0))
				bufferLine.append( black )
				drawnLine.append( black )
			self.buffer.append( bufferLine )
			self.drawn.append( drawnLine )
		self.updateRects = []
		self.fontCacheSoftLimit = fontCacheSoftLimit
		self.highlights = {}
		if fontCacheTolerance == None:
			self.fontCacheTolerance = self.fontCacheSoftLimit * 2
		else:
			self.fontCacheTolerance = fontCacheTolerance
	def clearscreen(self):
		self.buffer = []
		for j in range(self.cols):
			bufferLine = []
			for i in range(self.rows):
				black = ord(' '), ((0,0,0),(0,0,0))
				bufferLine.append( black )
			self.buffer.append( bufferLine )
	def nextEvent(self):
		for event in pygame.event.get():
			return PygameEvent( event )
	def update(self):
		if len( self.updateRects ) > (self.cols * self.rows // 2):
			pygame.display.flip()
		else:
			pygame.display.update( self.updateRects )
		self.updateRects = []
		## fontCache autoculls (?)
		### self.fontProvider.cull( self.fontCacheSoftLimit, self.fontCacheTolerance )
		t = time.time()
		while self.fpsUpdates and (self.fpsUpdates[0] + self.fpsPeriod) <= t:
			self.fpsUpdates.pop(0)
		self.fpsUpdates.append( time.time() )
	def fps(self):
		if (time.time()-self.started) < self.fpsPeriod: return None
		if not self.fpsUpdates: return 0
		return len(self.fpsUpdates) / self.fpsPeriod
	def cellRect(self, cr):
		x, y = cr[0]*self.cellWidth, cr[1]*self.cellHeight
		return pygame.Rect(x,y,self.cellWidth,self.cellHeight) 
	def fillCell(self, cr, colour):
		cellRect = self.cellRect( cr )
		self.screen.fill( colour, cellRect )
	def resetHighlights(self):
		self.highlights = {}
	def highlight(self, cr):
		try:
			self.highlights[cr] += 1
		except KeyError:
			self.highlights[cr] = 1
	def applyHighlights(self):
		for cr in self.highlights.keys():
			c, r = cr
			n, cp = self.buffer[c][r]
			cp = self.highlightColourPair( cp, self.highlights[cr] )
			self.buffer[c][r] = n, cp
		self.resetHighlights()
	def highlightColour(self, colour, hl):
		r,g,b = colour
		r,g,b = r+20*hl, g+20*hl, b+20*hl
		return min(255,r), min(255,g), min(255,b)
	def highlightColourPair(self, colourpair, hl):
		a, b = colourpair
		return self.highlightColour(a, hl), self.highlightColour(b, hl)
	def putLetter(self, cr, letter, colourpair ):
		col, row = cr
		self.buffer[col][row] = ord(letter), colourpair
	def alterBackgroundAt(self, cr, newBg ):
		col, row = cr
		ltr, fgbg = self.buffer[col][row] 
		fg, bg = fgbg
		self.buffer[col][row] = ltr, (fg,newBg)
	def putString(self, cr, string, colourpair ):
		col, row = cr
		for letter in string:
			if col >= self.cols: return
			self.buffer[col][row] = ord(letter), colourpair
			col += 1
	def getBackgroundColour(self, cr):
		col, row = cr
		ordl, cp = self.buffer[col][row]
		return cp[1]
	def drawCell(self, col, row):
		if self.buffer[col][row] != self.drawn[col][row]:
			x = col * self.cellWidth
			y = row * self.cellHeight
			index, cp = self.buffer[col][row]
			## uncommented this
			alpha = self.fontCache.getAlphabet( cp )
			self.screen.blit( alpha[index], (x,y) )
			## end uncommented
			### self.fontProvider.drawLetter( self.screen, (x,y), index, cp[0], cp[1] ) 
			self.drawn[col][row] = self.buffer[col][row]
			return True
		return False
	def drawRegion(self, rect):
		colBegin, rowBegin, width, height = rect
		dirty = False
		for col in range(colBegin, width + colBegin ):
			for row in range(rowBegin, height + rowBegin ):
				if self.drawCell( col, row ):
					dirty = True
		if dirty:
			rect = pygame.Rect( colBegin * self.cellWidth, rowBegin * self.cellHeight, width * self.cellWidth, height * self.cellHeight )
			self.updateRects.append( rect )
	def drawRegionCells(self, rect):
		colBegin, rowBegin, width, height = rect
		for col in range(colBegin, width + colBegin ):
			for row in range(rowBegin, height + rowBegin ):
				if self.drawCell( col, row ):
					rect = pygame.Rect( col * self.cellWidth, row * self.cellHeight, self.cellWidth, self.cellHeight )
					self.updateRects.append( rect )
	def drawCells(self):
		self.drawRegionCells( (0,0,self.cols,self.rows) )
	def drawRegionCols(self, rect):
		colBegin, rowBegin, width, height = rect
		for col in range(colBegin,width+colBegin):
			row = rowBegin
			dirty = False
			x = col * self.cellWidth
			while row < (height+rowBegin):
				if self.drawCell( col, row ):
					dirty = True
				row += 1
			if dirty:
				rect = pygame.Rect( x, rowBegin * self.cellHeight, self.cellWidth, self.cellHeight * height )
				self.updateRects.append( rect )

from rlinterface import Interface
from rlcore import Colour, Glyph
from rlkbinput import Key, KeyboardInput

class SdlInterface (Interface):
	def __init__(self, title, size, font, layout, forceResizable = False, keyboardRepeatDelay = 0.15 ):
		Interface.__init__(self, layout = layout, standardSize = size )

		self.sdlArgs = title, size, font, forceResizable

		# Set up some standard colour constants for backwards compatibility.
		Colour.black = (0,0,0)
		Colour.blue = (0,0,255)
		Colour.cyan = (0,255,255)
		Colour.green = (0,255,0)
		Colour.magenta = (255,0,255)
		Colour.red = (255,0,0)
		Colour.white = (255,255,255)
		Colour.yellow = (255,255,0)

		self.width, self.height = size # these will never change for us

		self.sdl = None

		self.keyboardRepeatDelay = keyboardRepeatDelay
		self.keyboardRepeatEvent = pygame.USEREVENT + 1
		self.keysHeld = {} # evt.key -> time

		self.inputQueue = []

		self.cursor = [0,0]

		self.postProcessors = []
	def start(self):
		title, size, font, forceResizable = self.sdlArgs
		self.sdl = PygameAscii( title, size, font, forceResizable, fontCacheSoftLimit = 50 )
		pygame.time.set_timer( self.keyboardRepeatEvent, int(0.5+1000 * self.keyboardRepeatDelay / 4.0) )
		self.layout()
	def preparePair(self, fgbg):
		pass # self.sdl.fontCache.preparePair( fgbg )
	def advanceCursor(self):
		self.cursor[0] += 1
	def move(self, x, y):
		self.cursor = [x,y]
	def mergeGlyph(self, glyph, x, y ):
		if glyph.bgColour:
			return glyph
		return Glyph( glyph.char, glyph.fgColour, self.sdl.getBackgroundColour( (x,y) ) )
	def put(self, glyph):
		self.sdl.putLetter( self.cursor, glyph.char, (glyph.fgColour,glyph.bgColour) )
		self.advanceCursor()
	def putBlanks(self, n):
		for i in range(n):
			self.sdl.putLetter( self.cursor, ' ', (Colour.white, Colour.black) ) 
			self.advanceCursor()
	def refresh(self):
#		import traceback
#		for x in traceback.format_stack(): print >> sys.stderr, x 
		self.drawElements()
		for postproc in self.postProcessors:
			postproc()
		self.sdl.applyHighlights()
		self.redraw()
	def highlight(self, x, y):
		self.sdl.highlight( (x,y) )
	def redraw(self):
		self.sdl.drawCells()
		self.sdl.update()
	def handleKeyDown(self, event):
		qevt = self.convertKeyboardEvent( event )
		self.keysHeld[ event.key ] = time.time(), qevt
		if qevt:
			self.inputQueue.append( qevt )
	def handleKeyUp(self, event):
		if self.keysHeld.has_key( event.key ):
			self.keysHeld.pop( event.key )
	def handleKeyRepeat(self):
		t = time.time()
		for key in self.keysHeld.keys():
			t0, event = self.keysHeld[key]
			if t - t0 > self.keyboardRepeatDelay:
				if event:
					self.inputQueue.append( event )
					self.keysHeld[key] = t, event
	def convertKeyboardEvent(self, event):
		# fix uppercase letters
		shifted = event.mod & (KMOD_LSHIFT | KMOD_RSHIFT)
		if event.unicode:
			return KeyboardInput( ascii = ord(event.unicode), shifted = shifted )
		if event.key >= 0 and event.key <= 255:
			ascii = event.key
			if event.mod & (KMOD_LSHIFT | KMOD_RSHIFT):
				ascii = ord( chr(ascii).upper() )
			if shifted and ascii == ord('>'): # this is the one shifted thing we really do need
				ascii = ord('<' )
			return KeyboardInput( ascii = ascii , shifted = shifted )
		if event.key == K_UP: return KeyboardInput( desc = Key.Up , shifted = shifted ) 
		if event.key == K_DOWN: return KeyboardInput( desc = Key.Down , shifted = shifted ) 
		if event.key == K_LEFT: return KeyboardInput( desc = Key.Left , shifted = shifted ) 
		if event.key == K_RIGHT: return KeyboardInput( desc = Key.Right , shifted = shifted ) 
		if event.key == K_HOME: return KeyboardInput( desc = Key.Home , shifted = shifted ) 
		if event.key == K_PAGEDOWN: return KeyboardInput( desc = Key.PageDown , shifted = shifted ) 
		if event.key == K_PAGEUP: return KeyboardInput( desc = Key.PageUp , shifted = shifted ) 
		if event.key == K_END: return KeyboardInput( desc = Key.End , shifted = shifted ) 
		if event.key == K_KP1: return KeyboardInput( desc = Key.Southwest , shifted = shifted )
		if event.key == K_KP2: return KeyboardInput( desc = Key.South , shifted = shifted )
		if event.key == K_KP3: return KeyboardInput( desc = Key.Southeast , shifted = shifted )
		if event.key == K_KP4: return KeyboardInput( desc = Key.West , shifted = shifted )
		if event.key == K_KP5: return KeyboardInput( desc = Key.Middle , shifted = shifted )
		if event.key == K_KP6: return KeyboardInput( desc = Key.East , shifted = shifted )
		if event.key == K_KP7: return KeyboardInput( desc = Key.Northwest , shifted = shifted )
		if event.key == K_KP8: return KeyboardInput( desc = Key.North , shifted = shifted )
		if event.key == K_KP9: return KeyboardInput( desc = Key.Northeast , shifted = shifted )
		return None
	def getInput(self):
		while not self.inputQueue:
			event = pygame.event.wait()
			if event.type == pygame.KEYDOWN:
				self.handleKeyDown( event )
			elif event.type == pygame.KEYUP:
				self.handleKeyUp( event )
			elif event.type == self.keyboardRepeatEvent:
				self.handleKeyRepeat()
			elif event.type == pygame.VIDEOEXPOSE:
				self.refresh()
			else:
				pass
		return self.inputQueue.pop(0)
	def quit(self):
		pygame.quit()
	def delay(self, ms = 100):
		pygame.time.delay( ms )
	def clearRepeats(self):
		pygame.event.clear( self.keyboardRepeatEvent )

class Highlighter: #ERL
	def __init__(self, mobile, interface):
		self.interface = interface
		self.mobile = mobile
	def __call__(self):
		if self.interface.elements["map"].priority == max( [ self.interface.elements[key].priority for key in self.interface.elements.keys() ] ):
			for x in range(self.mobile.level.width):
				for y in range(self.mobile.level.height):
					mob = self.mobile.level.tiles[x][y].mobile
					if not mob or not mob.paintHighlights: continue
					if not self.mobile.fov.isLit( mob.x, mob.y ): continue
					mob.highlightFacing( self.interface )

if __name__ == '__main__':
	pygame.init()
	screen = pygame.display.set_mode( (800,600) )
	font = MonochromeBitmapFont( "fonts/mono8x12.png" )
	font.drawLetter( screen, (10,10), ord('K'), (255,0,0), (160,64,64) )
	pygame.display.flip()
	time.sleep(2)
